/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package be.ac.ua.comp.projectGPT.players;

import be.ac.ua.comp.projectGPT.exceptions.DuplicateException;
import be.ac.ua.comp.projectGPT.exceptions.InsufficientFundsException;
import be.ac.ua.comp.projectGPT.exceptions.NonExistingAccountException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Keeps a Map of accounts that maps Person's with Long's (amount of money on the account).
 * Also provides methods to transfer money from one person to another, deposit money and withdraw money.
 */
public class Bank {

      Map accounts = Collections.synchronizedMap(new HashMap<Person,Long>());
      final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());

      public Bank() {
          logger.info("Creating");
      }

      public void addAccount(Person person,Long amount) throws DuplicateException {
          if(accounts.containsKey(person)) {
              throw new DuplicateException(person.getName());
          }
          logger.info("Adding account for "+person.getName()+" with funds "+amount);
          accounts.put(person, amount);
      }

      /**
       * Transfers money from one person to another.
       *
       * @param from The person that will get discredited.
       * @param to The person that will get credited.
       * @param i The money to be transferred.
       * @throws NonExistingAccountException Thrown when the account doesn't exist.
       */
      public synchronized void transfer(Person from, Person to,Long i) throws NonExistingAccountException, InsufficientFundsException {
        if(!accounts.containsKey(from)) {
            throw new NonExistingAccountException(from.getName());
        }
        if(!accounts.containsKey(to)) {
            throw new NonExistingAccountException(to.getName());
        }
        logger.info("Transferring "+i+" from "+from.getName()+" to "+to.getName());
        withdraw(from,i);
        deposit(to,i);
      }

      /**
       * Deposits money on a bank account.
       *
       * @param person The person who will be credited.
       * @param amount The amount of money to be credited.
       * @throws NonExistingAccountException Thrown when the account doesn't exist.
       */
      public synchronized void deposit(Person person,Long amount) throws NonExistingAccountException {
        if(!accounts.containsKey(person)) {
            throw new NonExistingAccountException(person.getName());
        }
        logger.info("Depositing "+amount+" to "+person.getName());
        accounts.put(person,(((Long) accounts.get(person)).longValue()+amount));
        logger.info("New balance for "+person.getName()+": "+accounts.get(person));
      }

      /**
       * Withdraws money from a bank account.
       *
       * @param person The person to be discredited.
       * @param amount The amount of money to be discredited.
       * @throws NonExistingAccountException Thrown when the account doesn't exist.
       */
      public synchronized void withdraw(Person person,Long amount) throws NonExistingAccountException, InsufficientFundsException {
          if(!accounts.containsKey(person)) {
              throw new NonExistingAccountException(person.getName());
          }
          if(!available(person,amount)) {
              throw new InsufficientFundsException(amount.toString());
          }
          logger.info("Withdrawing "+amount+" from "+ person.getName());
          accounts.put(person, (((Long) accounts.get(person)).longValue()-amount));
          logger.info("New balance for "+person.getName()+": "+accounts.get(person));
      }

      /**
       * Checks wether someone has a certain amount of money available.
       *
       * @param person The person to be checked.
       * @param amount The amount of money.
       * @return True/False depending on whether the account contains sufficient funds.
       */
      public boolean available(Person person,long amount) {
          if(!accounts.containsKey(person)) {
              return false;
          } else {
              return ((Long) accounts.get(person) > amount);
          }
      }

}
